﻿// ------------------------------------------------------------------------------
// <copyright file="TimeControl.cs" company="skumaronline.com">
//    Copyright (C) 2012 Suresh Kumar T.
//
//    This program is free software; you can redistribute it and/or
//    modify it under the terms of the GNU General Public License
//    as published by the Free Software Foundation version 2
//    of the License.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    http://www.gnu.org/licenses/gpl-2.0.html
//
// </copyright>
// ------------------------------------------------------------------------------

namespace StoreAppLib.Controls
{
    using System;
    using System.Collections.ObjectModel;
    using StoreAppLib.Animations;
    using Windows.UI;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Media.Animation;

    /// <summary>
    /// Time control
    /// </summary>
    public sealed class TimeControl : Control
    {
        /// <summary>
        /// Corner radius property
        /// </summary>
        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius", typeof(double), typeof(TimeControl), new PropertyMetadata(0));

        /// <summary>
        /// Auto select dependency property
        /// </summary>
        public static readonly DependencyProperty AutoSelectProperty =
            DependencyProperty.Register("AutoSelect", typeof(bool), typeof(TimeControl), new PropertyMetadata(false, OnAutoSelectPropertyChanged));

        /// <summary>
        /// Surface background property
        /// </summary>
        public static readonly DependencyProperty SurfaceBackgroundProperty =
            DependencyProperty.Register("SurfaceBackground", typeof(Brush), typeof(TimeControl), new PropertyMetadata(new SolidColorBrush(Colors.White)));

        /// <summary>
        /// Twelve hours or Twenty four hours property
        /// </summary>
        public static readonly DependencyProperty TwentyFourHourProperty =
            DependencyProperty.Register("TwentyFourHour", typeof(bool), typeof(TimeControl), new PropertyMetadata(true));

        /// <summary>
        /// Inner background property
        /// </summary>
        public static readonly DependencyProperty InnerBackgroundProperty =
            DependencyProperty.Register("InnerBackground", typeof(Brush), typeof(TimeControl), new PropertyMetadata(new SolidColorBrush(Colors.Transparent)));

        /// <summary>
        /// Button foreground property
        /// </summary>
        public static readonly DependencyProperty ButtonForegroundProperty =
            DependencyProperty.Register("ButtonForeground", typeof(Brush), typeof(TimeControl), new PropertyMetadata(new SolidColorBrush(Colors.Black)));

        /// <summary>
        /// Inner border brush property
        /// </summary>
        public static readonly DependencyProperty InnerBorderBrushProperty =
            DependencyProperty.Register("InnerBorderBrush", typeof(Brush), typeof(TimeControl), new PropertyMetadata(new SolidColorBrush(Colors.Black)));

        /// <summary>
        /// Header foreground property
        /// </summary>
        public static readonly DependencyProperty HeaderForegroundProperty =
            DependencyProperty.Register("HeaderForeground", typeof(Brush), typeof(TimeControl), new PropertyMetadata(new SolidColorBrush(Colors.Black)));

        /// <summary>
        /// Action button visibility
        /// </summary>
        private Visibility actionButtonVisibility;

        /// <summary>
        /// The select button
        /// </summary>
        private Button select;

        /// <summary>
        /// The cancel button
        /// </summary>
        private Button cancel;

        /// <summary>
        /// The meridiem control
        /// </summary>
        private MeridiemScroller meridiem;

        /// <summary>
        /// The hour control
        /// </summary>
        private TimeScroller hour;

        /// <summary>
        /// The minute control
        /// </summary>
        private TimeScroller minute;

        /// <summary>
        /// The second control
        /// </summary>
        private TimeScroller second;

        /// <summary>
        /// Hour down
        /// </summary>
        private TextBlock hourDown;

        /// <summary>
        /// Minute down
        /// </summary>
        private TextBlock minuteDown;

        /// <summary>
        /// Second down
        /// </summary>
        private TextBlock secondDown;

        /// <summary>
        /// Meridiem down
        /// </summary>
        private TextBlock meridiemDown;

        /// <summary>
        /// Hour up
        /// </summary>
        private TextBlock hourUp;

        /// <summary>
        /// Minute up
        /// </summary>
        private TextBlock minuteUp;

        /// <summary>
        /// Second up
        /// </summary>
        private TextBlock secondUp;

        /// <summary>
        /// Meridiem up
        /// </summary>
        private TextBlock meridiemUp;

        /// <summary>
        /// Whether pointer down or not
        /// </summary>
        private bool isPointer = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeControl" /> class
        /// </summary>
        public TimeControl()
        {
            this.DefaultStyleKey = typeof(TimeControl);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeControl" /> class
        /// </summary>
        /// <param name="time">The default time</param>
        public TimeControl(DateTime time)
            : this()
        {
            this.Time = time;
            this.NewTime = time;
        }

        /// <summary>
        /// Event to handle time change
        /// </summary>
        public event DateTimeChangedEventHandler TimeChanged;

        /// <summary>
        /// Gets or sets header foreground
        /// </summary>
        public Brush HeaderForeground
        {
            get { return (Brush)this.GetValue(HeaderForegroundProperty); }
            set { this.SetValue(HeaderForegroundProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to update time on scrolling automatically
        /// </summary>
        public bool AutoSelect
        {
            get { return (bool)this.GetValue(AutoSelectProperty); }
            set { this.SetValue(AutoSelectProperty, value); }
        }

        /// <summary>
        /// Gets or sets control background
        /// </summary>
        public Brush SurfaceBackground
        {
            get { return (Brush)this.GetValue(SurfaceBackgroundProperty); }
            set { this.SetValue(SurfaceBackgroundProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether twenty four hours or twelve hours format
        /// </summary>
        public bool TwentyFourHour
        {
            get { return (bool)this.GetValue(TwentyFourHourProperty); }
            set { this.SetValue(TwentyFourHourProperty, value); }
        }

        /// <summary>
        /// Gets or sets inner background
        /// </summary>
        public Brush InnerBackground
        {
            get { return (Brush)this.GetValue(InnerBackgroundProperty); }
            set { this.SetValue(InnerBackgroundProperty, value); }
        }

        /// <summary>
        /// Gets or sets button foreground
        /// </summary>
        public Brush ButtonForeground
        {
            get { return (Brush)this.GetValue(ButtonForegroundProperty); }
            set { this.SetValue(ButtonForegroundProperty, value); }
        }

        /// <summary>
        /// Gets or sets inner border brush
        /// </summary>
        public Brush InnerBorderBrush
        {
            get { return (Brush)this.GetValue(InnerBorderBrushProperty); }
            set { this.SetValue(InnerBorderBrushProperty, value); }
        }

        /// <summary>
        /// Gets or sets corner radius
        /// </summary>
        public double CornerRadius
        {
            get { return (double)this.GetValue(CornerRadiusProperty); }
            set { this.SetValue(CornerRadiusProperty, value); }
        }

        /// <summary>
        /// Gets the updated time on scrolling
        /// </summary>
        public DateTime NewTime { get; private set; }

        /// <summary>
        /// Gets the default time
        /// </summary>
        public DateTime Time { get; private set; }

        /// <summary>
        /// Initialize control on applying template
        /// </summary>
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.meridiem = this.GetTemplateChild("meridiem") as MeridiemScroller;
            this.hour = this.GetTemplateChild("hour") as TimeScroller;
            this.minute = this.GetTemplateChild("minute") as TimeScroller;
            this.second = this.GetTemplateChild("second") as TimeScroller;

            this.select = this.GetTemplateChild("select") as Button;
            this.cancel = this.GetTemplateChild("cancel") as Button;

            this.hourDown = this.GetTemplateChild("hourDown") as TextBlock;
            this.minuteDown = this.GetTemplateChild("minuteDown") as TextBlock;
            this.secondDown = this.GetTemplateChild("secondDown") as TextBlock;
            this.meridiemDown = this.GetTemplateChild("meridiemDown") as TextBlock;

            this.hourUp = this.GetTemplateChild("hourUp") as TextBlock;
            this.minuteUp = this.GetTemplateChild("minuteUp") as TextBlock;
            this.secondUp = this.GetTemplateChild("secondUp") as TextBlock;
            this.meridiemUp = this.GetTemplateChild("meridiemUp") as TextBlock;

            this.hourDown.PointerPressed += this.OnPointerPressed;
            this.hourDown.PointerReleased += this.OnPointerReleased;
            this.minuteDown.PointerPressed += this.OnPointerPressed;
            this.minuteDown.PointerReleased += this.OnPointerReleased;
            this.secondDown.PointerPressed += this.OnPointerPressed;
            this.secondDown.PointerReleased += this.OnPointerReleased;
            this.meridiemDown.PointerPressed += this.OnPointerPressed;
            this.meridiemDown.PointerReleased += this.OnPointerReleased;

            this.hourUp.PointerPressed += this.OnPointerPressed;
            this.hourUp.PointerReleased += this.OnPointerReleased;
            this.minuteUp.PointerPressed += this.OnPointerPressed;
            this.minuteUp.PointerReleased += this.OnPointerReleased;
            this.secondUp.PointerPressed += this.OnPointerPressed;
            this.secondUp.PointerReleased += this.OnPointerReleased;
            this.meridiemUp.PointerPressed += this.OnPointerPressed;
            this.meridiemUp.PointerReleased += this.OnPointerReleased;

            this.hourUp.Tapped += this.OnArrowTapped;
            this.minuteUp.Tapped += this.OnArrowTapped;
            this.secondUp.Tapped += this.OnArrowTapped;
            this.meridiemUp.Tapped += this.OnArrowTapped;
            this.hourDown.Tapped += this.OnArrowTapped;
            this.minuteDown.Tapped += this.OnArrowTapped;
            this.secondDown.Tapped += this.OnArrowTapped;
            this.meridiemDown.Tapped += this.OnArrowTapped;

            this.select.Tapped += this.OnActionButtonTapped;
            this.cancel.Tapped += this.OnActionButtonTapped;

            StackPanel actionPanel = this.GetTemplateChild("actionPanel") as StackPanel;
            actionPanel.Visibility = this.actionButtonVisibility;

            this.hour.MaxValue = this.TwentyFourHour ? 23 : 12;
            this.hour.MinValue = this.TwentyFourHour ? 0 : 1;

            int h;
            if (this.TwentyFourHour)
            {
                h = this.Time.Hour;
            }
            else
            {
                h = this.Time.Hour - 12;

                if (h == 0)
                {
                    h = 12;
                }
            }

            this.hour.Value = h;
            this.minute.Value = this.Time.Minute;
            this.second.Value = this.Time.Second;

            this.meridiem.Value = this.Time.Hour >= 12 ? "PM" : "AM";

            this.meridiem.Visibility = this.TwentyFourHour ? Visibility.Collapsed : Visibility.Visible;
            this.meridiemDown.Visibility = this.TwentyFourHour ? Visibility.Collapsed : Visibility.Visible;
            this.meridiemUp.Visibility = this.TwentyFourHour ? Visibility.Collapsed : Visibility.Visible;

            this.hour.ValueChanged += this.OnTimeValueChanged;
            this.minute.ValueChanged += this.OnTimeValueChanged;
            this.second.ValueChanged += this.OnTimeValueChanged;
            this.meridiem.ValueChanged += this.OnTimeValueChanged;
        }

        /// <summary>
        /// Fires when pointer enter this control. Apply pointer over effect
        /// </summary>
        /// <param name="e">The argument</param>
        protected override void OnPointerEntered(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            e.Handled = true;
            base.OnPointerEntered(e);
            VisualStateManager.GoToState(this, "MouseIn", true);
        }

        /// <summary>
        /// Fires when pointer moved over this control. Apply pointer over effect
        /// </summary>
        /// <param name="e">The argument</param>
        protected override void OnPointerMoved(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            this.isPointer = true;
            e.Handled = true;
            base.OnPointerMoved(e);
            VisualStateManager.GoToState(this, "MouseOut", true);
            VisualStateManager.GoToState(this, "MouseIn", true);
        }

        /// <summary>
        /// Fires when pointer existed. Apply default effect
        /// </summary>
        /// <param name="e">The argument</param>
        protected override void OnPointerExited(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            this.isPointer = false;
            e.Handled = true;
            base.OnPointerExited(e);
            VisualStateManager.GoToState(this, "MouseOut", true);
        }

        /// <summary>
        /// Fires when auto select property changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">Property change argument</param>
        private static void OnAutoSelectPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            TimeControl timeControl = source as TimeControl;

            if (timeControl != null)
            {
                timeControl.actionButtonVisibility = (bool)args.NewValue ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        /// <summary>
        /// Update time on when controls scrolled
        /// </summary>
        /// <param name="sender">The control scrolled</param>
        /// <param name="value">New value</param>
        private void OnTimeValueChanged(object sender, object value)
        {
            if (sender == this.hour)
            {
                int h24 = this.hour.Value;

                if (!this.TwentyFourHour)
                {
                    if (this.meridiem.Value == "PM")
                    {
                        h24 += 12;

                        if (h24 == 24)
                        {
                            h24 = 0;
                        }
                    }
                    else if (h24 == 12)
                    {
                        h24 = 0;
                    }
                }

                this.NewTime = this.NewTime.AddHours(-1 * this.NewTime.Hour).AddHours(h24);
            }
            else if (sender == this.minute)
            {
                this.NewTime = this.NewTime.AddMinutes(-1 * this.NewTime.Minute).AddMinutes(this.minute.Value);
            }
            else if (sender == this.second)
            {
                this.NewTime = this.NewTime.AddSeconds(-1 * this.NewTime.Second).AddSeconds(this.second.Value);
            }
            else if (sender == this.meridiem)
            {
                if (this.NewTime.Hour >= 12 && this.meridiem.Value == "AM")
                {
                    this.NewTime = this.NewTime.AddHours(-12);
                }
                else if (this.NewTime.Hour < 12 && this.meridiem.Value == "PM")
                {
                    this.NewTime = this.NewTime.AddHours(12);
                }
            }

            if (this.Time != this.NewTime && this.TimeChanged != null && this.AutoSelect)
            {
                this.Time = this.NewTime;

                this.TimeChanged(this, new DateTimeEventArgs(this.Time));
            }
        }

        /// <summary>
        /// Fire when select or cancel button tapped
        /// </summary>
        /// <param name="sender">The button tapped</param>
        /// <param name="e">The tap event argument</param>
        private void OnActionButtonTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            Button button = sender as Button;

            if (button.Name == "select")
            {
                if (this.TimeChanged != null)
                {
                    this.Time = this.NewTime;

                    this.TimeChanged(this, new DateTimeEventArgs(this.Time));
                }
            }

            PopupManager popupManager = this.Parent as PopupManager;
            popupManager.Close();
        }

        /// <summary>
        /// Fires when pointer pressed. Apply down effect
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The argument</param>
        private void OnPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            e.Handled = true;
            FrameworkElement element = sender as FrameworkElement;
            element.CapturePointer(e.Pointer);

            AnimationHelper.ColorAnimation(sender as DependencyObject, "(TextBlock.Foreground).(SolidColorBrush.Color)", Color.FromArgb(50, 0, 0, 0), 0);
        }

        /// <summary>
        /// Fires when pressed pointer released. Apply default effect
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The argument</param>
        private void OnPointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            e.Handled = true;
            FrameworkElement element = sender as FrameworkElement;

            if (this.isPointer)
            {
                VisualStateManager.GoToState(this, "MouseOut", true);
                VisualStateManager.GoToState(this, "MouseIn", true);
            }
            else
            {
                VisualStateManager.GoToState(this, "MouseOut", true);
            }

            element.ReleasePointerCapture(e.Pointer);
        }

        /// <summary>
        /// Fires when up or down arrow tapped
        /// </summary>
        /// <param name="sender">The arrow text block</param>
        /// <param name="e">Tap event</param>
        private void OnArrowTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {            
            if (sender == this.hourUp)
            {
                this.hour.Scroll(false);
            }
            else if (sender == this.hourDown)
            {
                this.hour.Scroll(true);
            }
            else if (sender == this.minuteUp)
            {
                this.minute.Scroll(false);
            }
            else if (sender == this.minuteDown)
            {
                this.minute.Scroll(true);
            }
            else if (sender == this.secondUp)
            {
                this.second.Scroll(false);
            }
            else if (sender == this.secondDown)
            {
                this.second.Scroll(true);
            }
            else if (sender == this.meridiemUp)
            {
                this.meridiem.Scroll(false);
            }
            else if (sender == this.meridiemDown)
            {
                this.meridiem.Scroll(true);
            }
        }
    }
}
